package cn.uc.ucgc.community.upgrade;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.dom4j.Document;
import org.dom4j.io.XMLWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.uc.ucgc.community.upgrade.conf.MacroItem;

/**
 * 工具类
 * 
 * @author yuyj@ucweb.com
 * @createDate 2014-1-3
 *
 */
public final class Utils {
    private static final Logger logger = LoggerFactory.getLogger(Utils.class);
    private static final Pattern pttn = Pattern.compile("^\\s*[#!].*");
    private static final Pattern pttnKey = Pattern.compile("^\\s*([^=]+)=");
    private static final Pattern pttnMacroKey = Pattern.compile("^\\$\\{([a-zA-Z\\-_\\.0-9]+)\\}$");
    private static final String CLS_SUFFIX = ".class";
    private static final List<String> EMPTY_LIST = new ArrayList<String>(0);

    /**
     * 根据给定的父路径信息、文件后缀名，按ASCII码排序，返回改目录下所有文件的路径信息
     * @param parent 待搜索文件夹路径
     * @param suffix 待搜索的文件后缀名，例如：.sql,.sh,.bat
     * @return
     */
    public static List<String> getSortedFilePath(File parent, String suffix) {
        //首先判断对应的文件是否存在
        if (null == parent || !parent.exists()) {
            logger.warn("{} not exist!", parent);
            return EMPTY_LIST;
        }
        if (parent.isFile()) {
            logger.warn("{} must be a directory!", parent);
            return EMPTY_LIST;
        }
        //循环遍历当前文件夹下的文件，并将满足条件的文件过滤出来
        File[] files = parent.listFiles();
        if (null == files || files.length < 1) {
            return EMPTY_LIST;
        }
        List<String> rstLst = new ArrayList<String>();
        for (File file : files) {
            //取出文件的后缀
            String filePath = file.getAbsolutePath();
            if (filePath.endsWith(suffix)) {
                rstLst.add(filePath);
            }
        }
        //做排序
        Collections.sort(rstLst);
        return rstLst;
    }

    public static void exeShellCmd(File homeDir, String shellCmd, String[] params)
            throws IOException {
        //将命令及参数封装成List
        List<String> cmdLst = new ArrayList<String>();
        cmdLst.add(shellCmd);
        if (null != params && params.length > 0) {
            for (String p : params) {
                cmdLst.add(p);
            }

        }
        ProcessBuilder pb = new ProcessBuilder(cmdLst);
        //设置环境变量
        Map<String, String> curEnv = pb.environment();
        Map<String, String> myEnv = getEnvMap();
        curEnv.putAll(myEnv);
        pb.redirectErrorStream(true);
        //设置工作目录
        if (null != homeDir && homeDir.exists() && homeDir.isDirectory()) {
            logger.warn("set home dir:{}", homeDir.getAbsolutePath());
            pb.directory(homeDir);
        }
        logger.info("start to exe:{},parms:{}", shellCmd, Arrays.toString(params));
        Process prcss = pb.start();
        InputStream is = null;
        try {
            //获取新进程输出信息
            is = prcss.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is, Constants.DEF_CHARSET));
            StringBuilder sb = new StringBuilder();
            String info = br.readLine();
            while (null != info) {
                sb.append(info).append(Constants.LINE_SEPARATOR);
                info = br.readLine();
            }
            //等待脚本执行完成
            logger.info("waiting exe shell cmd ...");
            prcss.waitFor();
            int exitVal = prcss.exitValue();
            logger.info("cmd exitVal:{},cmd output: \n{}", exitVal, sb.toString());
        } catch (Exception e) {
            logger.error("", e);
        } finally {
            closeFileStream(is);
        }
    }

    private static Map<String, String> getEnvMap() {
        //待实现
        return new HashMap<String, String>();
    }

    public static void exeDbSql(String sqlPath, Logger logger) {
        //TODO:执行sql脚本，可能涉及用户输入账号/密码
        //通过ProcessBuilder实现，暂不实现
    }

    public static void recordTaskBegin(Logger logger, String taskDesc) {
        StringBuilder sb = new StringBuilder();
        if (logger.isInfoEnabled()) {
            sb.append(Constants.TASK_RESULT_BEGIN).append(Constants.LINE_SEPARATOR);
            sb.append(taskDesc).append(Constants.LINE_SEPARATOR);
            logger.info(sb.toString());
        }
    }

    public static void recordTaskEnd(Logger logger) {
        if (logger.isInfoEnabled()) {
            logger.info(Constants.TASK_RESULT_END + Constants.LINE_SEPARATOR);
        }
    }

    public static void closeFileStream(Closeable stream) {
        if (null == stream) {
            return;
        }
        try {
            stream.close();
        } catch (Exception e) {
            //ignore
        }
    }

    /**
     * 判断是否是prop配置文件的注释信息
     * @param txt
     * @return
     */
    public static boolean isPropFileComment(String txt) {
        if (null == txt) {
            return false;
        }
        Matcher matcher = pttn.matcher(txt);
        return matcher.find();
    }

    /**
     * 提取Prop配置文件的Key信息
     * @param txt
     * @return
     */
    public static String getPropFileKey(String txt) {
        if (null == txt) {
            return null;
        }
        Matcher matcher = pttnKey.matcher(txt);
        if (!matcher.find()) {
            return null;
        }
        return matcher.group(1);
    }

    public static void saveDom2Xml(Document srcDom, File filePath) throws IOException {
        //至此，目标dom已经更新完了，需要写如文件
        XMLWriter writer = null;
        try {
            writer = new XMLWriter(new OutputStreamWriter(new FileOutputStream(filePath),
                    Constants.DEF_CHARSET));
            writer.write(srcDom);
            writer.flush();
        } finally {
            try {
                if (null != writer) {
                    writer.close();
                }
            } catch (Exception e) {
                // ignore
            }
        }
    }

    public static void addConf2PropFile(File path, Map<String, String> addProps) {
        Set<Entry<String, String>> entrySet = addProps.entrySet();
        Iterator<Entry<String, String>> it = entrySet.iterator();
        boolean needWrite2File = false;

        StringBuilder sb = new StringBuilder(128);
        sb.append(Constants.LINE_SEPARATOR);
        while (it.hasNext()) {
            Entry<String, String> entry = it.next();
            sb.append(entry.getKey()).append("=").append(entry.getValue())
                    .append(Constants.LINE_SEPARATOR);
            needWrite2File = true;
        }

        if (true == needWrite2File) {
            append2File(path, sb.toString());
        }
    }

    public static void append2File(File file, String content) {
        try {
            OutputStreamWriter read = new OutputStreamWriter(new FileOutputStream(file, true),
                    "UTF-8");
            BufferedWriter output = new BufferedWriter(read);
            output.write(content);
            output.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void modifyPropFile(File path, Map<String, String> replaceProps)
            throws IOException {
        BufferedReader bR = null;
        BufferedWriter bW = null;
        try {
            StringBuilder sb = new StringBuilder(1024);
            //从头部重取数据
            bR = new BufferedReader(new InputStreamReader(new FileInputStream(path),
                    Constants.DEF_CHARSET));
            String tmpStr = bR.readLine();
            boolean needReadNextLine = false;
            while (null != tmpStr) {
                if (needReadNextLine) {
                    sb.append(tmpStr).append(Constants.LINE_SEPARATOR);
                    needReadNextLine = tmpStr.endsWith("\\");
                    tmpStr = bR.readLine();
                    continue;
                }
                //1)判断是否是注释
                if (Utils.isPropFileComment(tmpStr)) {
                    //如果是注释就直接写
                    sb.append(tmpStr).append(Constants.LINE_SEPARATOR);
                    //是否后缀为\
                    needReadNextLine = tmpStr.endsWith("\\");
                    tmpStr = bR.readLine();
                    continue;
                }
                //2)取出key,value
                String key = Utils.getPropFileKey(tmpStr);
                if (null == key || !replaceProps.containsKey(key)) {
                    //直接写这些数据
                    sb.append(tmpStr).append(Constants.LINE_SEPARATOR);
                    needReadNextLine = tmpStr.endsWith("\\");
                    tmpStr = bR.readLine();
                    continue;
                }
                //如果是需要替换的key
                sb.append(key).append("=").append(replaceProps.get(key))
                        .append(Constants.LINE_SEPARATOR);
                //如之前的value值含\，这里直接踢出
                while (tmpStr.endsWith("\\")) {
                    tmpStr = bR.readLine();
                }
                tmpStr = bR.readLine();
            }
            //执行完之后，需要保存对应的文件数据
            bW = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path),
                    Constants.DEF_CHARSET));
            bW.write(sb.toString());
        } finally {
            closeFileStream(bR);
            closeFileStream(bW);
        }
    }

    /**
     * 用于删除配置文件中的某些配置
     * @param path
     * @param deleteProps
     * @throws IOException
     */
    public static void deletePropFile(File path, Set<String> deleteProps) throws IOException {
        BufferedReader bR = null;
        BufferedWriter bW = null;
        try {
            StringBuilder sb = new StringBuilder(1024);
            //从头部重取数据
            bR = new BufferedReader(new InputStreamReader(new FileInputStream(path),
                    Constants.DEF_CHARSET));
            String tmpStr = bR.readLine();
            boolean needReadNextLine = false;
            while (null != tmpStr) {
                if (needReadNextLine) {
                    sb.append(tmpStr).append(Constants.LINE_SEPARATOR);
                    needReadNextLine = tmpStr.endsWith("\\");
                    tmpStr = bR.readLine();
                    continue;
                }
                //1)判断是否是注释
                if (Utils.isPropFileComment(tmpStr)) {
                    //如果是注释就直接写
                    sb.append(tmpStr).append(Constants.LINE_SEPARATOR);
                    //是否后缀为\
                    needReadNextLine = tmpStr.endsWith("\\");
                    tmpStr = bR.readLine();
                    continue;
                }
                //2)取出key
                String key = Utils.getPropFileKey(tmpStr);
                if (null == key || !deleteProps.contains(key)) {
                    //直接写这些数据
                    sb.append(tmpStr).append(Constants.LINE_SEPARATOR);
                    needReadNextLine = tmpStr.endsWith("\\");
                    tmpStr = bR.readLine();
                    continue;
                }
                //如果是需要删除的key，直接不再写入即可。

                //如之前的value值含\，这里直接踢出
                while (tmpStr.endsWith("\\")) {
                    tmpStr = bR.readLine();
                }
                tmpStr = bR.readLine();
            }
            //执行完之后，需要保存对应的文件数据
            bW = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path),
                    Constants.DEF_CHARSET));
            bW.write(sb.toString());
        } finally {
            closeFileStream(bR);
            closeFileStream(bW);
        }
    }

    /**
     * 收集宏配置保存到对应的文件中
     * @param path
     * @param macroItems
     * @throws IOException
     */
    public static void saveProp2File(File path, List<MacroItem> macroItems) throws IOException {
        BufferedWriter bW = null;
        try {
            StringBuilder sb = new StringBuilder(1024);
            if (macroItems == null || macroItems.isEmpty()) {
                sb.append("# no macro defined in conf file");
            } else {
                Set<String> macroKeys = new HashSet<String>();
                for (MacroItem item : macroItems) {
                    if (null == item) {
                        continue;
                    }
                    //注释
                    sb.append("# ").append(item.getTip()).append(Constants.LINE_SEPARATOR);
                    if (macroKeys.contains(item.getMacroKey())) {
                        //说明之前有对应的宏配置，需要警示
                        sb.append("# ").append("warning duplicate macro key: ")
                                .append(item.getMacroKey()).append(Constants.LINE_SEPARATOR);
                    } else {
                        macroKeys.add(item.getMacroKey());
                    }
                    //key=value
                    sb.append(item.getMacroKey()).append("=").append(item.getExample())
                            .append(Constants.LINE_SEPARATOR);
                }
            }

            //执行完之后，需要保存对应的文件数据
            bW = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path),
                    Constants.DEF_CHARSET));
            bW.write(sb.toString());
        } finally {
            closeFileStream(bW);
        }
    }

    public static String getMacroKey(String txt) {
        if (null == txt || txt.isEmpty()) {
            return null;
        }
        Matcher matcher = pttnMacroKey.matcher(txt);
        if (!matcher.find()) {
            return null;
        }
        return matcher.group(1);
    }

    /**
     * 
     * 1）递归获取当前文件夹下所有类文件信息；
     * 2）获取当前父目录下jar包文件，或类文件信息；
     * @param parentPath 父路径
     * @return 满足条件的URL
     * @throws MalformedURLException 
     */
    public static List<URL> getClassFiles(File parentPath, List<String> allClsLst)
            throws MalformedURLException {
        List<URL> rstLst = new ArrayList<URL>();
        if (null == parentPath || !parentPath.exists() || parentPath.isFile()) {
            return rstLst;
        }
        rstLst.add(parentPath.toURI().toURL());
        String homePath = parentPath.toURI().toURL().toString();
        //1）先在当前级别下获取对应的jar包文件
        File[] files = parentPath.listFiles();
        if (null == files || files.length < 1) {
            return rstLst;
        }

        for (File file : files) {
            if (file.isFile()) {
                //看是否是jar包文件
                String filePath = file.getAbsolutePath();
                //针对类，需要提取出类的全路径名称
                if (filePath.endsWith(CLS_SUFFIX)) {
                    collecClsInfo(homePath, file, rstLst, allClsLst);
                    continue;
                }
                //针对jar包，要遍历其文件中所有类资源，并获取对应的类全路径信息
                if (filePath.endsWith(".jar")) {
                    try {
                        rstLst.add(file.toURI().toURL());
                        //需要收集jar包中所有类全路径信息
                        JarFile jarFile = new JarFile(file);
                        Enumeration<JarEntry> entrys = jarFile.entries();
                        while (entrys.hasMoreElements()) {
                            JarEntry jarEntry = entrys.nextElement();
                            String clsName = jarEntry.getName();
                            if (clsName.endsWith(CLS_SUFFIX)) {
                                allClsLst.add(clsName.substring(0,
                                        clsName.length() - CLS_SUFFIX.length()).replace('/', '.'));
                            }
                        }
                    } catch (Exception e) {
                        logger.error("", e);
                    }

                }
                continue;
            }
            //如果是文件夹，则递归遍历其下的class文件
            recursionGetClass(homePath, file, rstLst, allClsLst);
        }
        return rstLst;
    }

    private static String getClsFullPath(String homePath, String clsFilePath) {
        int bIndex = homePath.length();
        int eIndex = clsFilePath.length() - CLS_SUFFIX.length();
        String clsName = clsFilePath.substring(bIndex, eIndex);
        return clsName.replace('/', '.');
    }

    private static void recursionGetClass(String homePath, File parent, List<URL> rstLst,
            List<String> allClsLst) {

        File[] files = parent.listFiles();
        if (null == files || files.length < 1) {
            return;
        }
        for (File file : files) {
            if (file.isFile()) {
                //看是否是class文件
                String filePath = file.getAbsolutePath();
                if (filePath.endsWith(CLS_SUFFIX)) {
                    collecClsInfo(homePath, file, rstLst, allClsLst);
                }
                continue;
            }
            //如果是文件夹，则递归遍历其下的class文件
            recursionGetClass(homePath, file, rstLst, allClsLst);
        }
    }

    private static void collecClsInfo(String homePath, File file, List<URL> rstLst,
            List<String> allClsLst) {
        try {
            URL url = file.toURI().toURL();
            rstLst.add(url);
            allClsLst.add(getClsFullPath(homePath, url.toString()));
        } catch (Exception e) {
            logger.error("", e);
        }
    }

    public static Properties getPropValues(File propFile) throws IOException {
        //外部应确保该宏配置文件存在，此处逻辑不做校验
        InputStream srcIS = null;
        Properties srcProp = new Properties();
        try {
            srcIS = new FileInputStream(propFile);
            srcProp.load(srcIS);
        } finally {
            Utils.closeFileStream(srcIS);
        }
        return srcProp;
    }

}
